package com.rzjm.exercise.common.utils;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;

import lombok.extern.slf4j.Slf4j;

/**
 * @Author felix
 * @Date 2018/11/6 10:28
 */
@Slf4j
public class BeanUtils {

    public static final <T, S> void copyProperties(Collection<S> provider, Class<S> providerType, List<T> accepter, Class<T> accepterType) {
        for (@SuppressWarnings("unused") S instance : provider) {
            final S provicerValue = ClassUtils.newInstance(providerType);
            final T accepterValue = ClassUtils.newInstance(accepterType);
            copyProperties(provicerValue, accepterValue);
        }
    }

    public static final void copyProperties(Object provider, Object accepter) {
        final Class<?> providerCls = provider.getClass();
        final Class<?> accepterCls = accepter.getClass();

        for (Class<?> cls = providerCls; cls != null; cls = cls.getSuperclass()) {
            for (Field providerField : cls.getDeclaredFields()) {
                final String propertyName = providerField.getName();

                final Method providerReader = MethodUtils.getReadMethod(cls, propertyName);

                if (providerReader == null) {
                    continue;
                }

                final Method accepterWriter = MethodUtils.getWriteMethod(accepterCls, propertyName);

                if (accepterWriter == null) {
                    continue;
                }


                final Class<?> parameterType = accepterWriter.getParameterTypes()[0];

                Object paramValue = ConvertUtils.convert(MethodUtils.invokeMethod(provider, providerReader), parameterType);

                if (paramValue == null) {
                    /** ignore **/
                } else {
                    MethodUtils.invokeMethod(accepter, accepterWriter, paramValue);
                }
            }
        }
    }

    public static Map<String, Object> transBean2Map(Object obj) {
        if(obj == null) {
            return null;
        }

        Map<String, Object> map = new HashMap<>();
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();

                // 过滤class属性
                if (!"class".equals(key)) {
                    // 得到property对应的getter方法
                    Method getter = property.getReadMethod();
                    Object value = getter.invoke(obj);

                    map.put(key, value);
                }
            }
        } catch (Exception e) {
            log.error("transBean2Map Error " + e);
        }

        return map;
    }

	/**
	 * 复制{@link HttpServletRequest}的Param的值，用{@code Setter}方法赋值到Param的名称对应的目标{@link Object}的成员变量上
	 * 其中如果{@code prefix}和{@code suffix}非空的话，就要从Param的名称中去掉前后缀后比较
	 *
	 * @param request 提供值的{@link HttpServletRequest}实例
	 * @param dest 接受数据的{@link Object}实例
	 * @param prefix 前缀
	 * @param suffix 后缀
	 */
	public static final void copyProperties(final HttpServletRequest request, final Object accepter, final String prefix, final String suffix) {
		//注册一个日期转换器
		ConvertUtils.register(new Converter() {
			@Override
			@SuppressWarnings("rawtypes")
			public Object convert(Class type, Object value) {
				Date date = null;
				if (value instanceof String) {
					String dateString = (String) value;
					SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
					try {
						if (!StringUtils.isBlank(dateString)) {
							date = sdf.parse(dateString);
						}
					} catch (ParseException e) {
						e.printStackTrace();
					}
				} else if (value instanceof Date) {
					return value;
				}
				return date;
			}
		}, Date.class);

		ConvertUtils.register(new Converter() {
			@Override
			@SuppressWarnings("rawtypes")
			public Object convert(Class type, Object value) {
				if (value instanceof String) {
			        try {
			            return Long.valueOf((String) value);
			        } catch (final NumberFormatException nfe) {
			            return null;
			        }
				} else if (value instanceof Long) {
					return value;
				}
				return null;
			}
		}, Long.class);

		ConvertUtils.register(new Converter() {
			@Override
			@SuppressWarnings("rawtypes")
			public Object convert(Class type, Object value) {
				if (value instanceof String) {
			        try {
			            return Integer.valueOf((String) value);
			        } catch (final NumberFormatException nfe) {
			            return null;
			        }
				} else if (value instanceof Integer) {
					return value;
				}
				return null;
			}
		}, Integer.class);

		final Enumeration<String> keys = request.getParameterNames();

		final String vPrefix = ((StringUtils.isBlank(prefix)) ? "" : prefix);
		final String vSuffix = ((StringUtils.isBlank(suffix)) ? "" : suffix);

		final Class<?> cls = accepter.getClass();

		while(keys.hasMoreElements()) {
			final String key = keys.nextElement();

			if (!StringUtils.startsWithIgnoreCase(key, vPrefix) || !StringUtils.endsWithIgnoreCase(key, vSuffix)) {
				continue;
			}

			final String name = StringUtils.removeEnd(StringUtils.removeStart(key.toLowerCase(), vPrefix.toLowerCase()), vSuffix.toLowerCase());

			if (StringUtils.isBlank(name))
				continue;

			final String value = request.getParameter(key);

			final Method writer = MethodUtils.getWriteMethod(cls, name);

			if (writer != null && value != null) {
				final Class<?> parameterType = writer.getParameterTypes()[0];

				Object paramValue = ConvertUtils.convert(value, parameterType);

				if (paramValue != null) {
					MethodUtils.invokeMethod(accepter, writer, paramValue);
				}
			}
		}
	}
}
